package crmdna.participant;

import static crmdna.common.OfyService.ofy;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.mail.MessagingException;

import com.googlecode.objectify.Key;

import crmdna.client.Client;
import crmdna.common.APIException;
import crmdna.common.APIResponse.Status;
import crmdna.common.Utils;
import crmdna.contactdetail.ContactDetail;
import crmdna.contactdetail.ContactDetailProp;
import crmdna.email.EmailProp;
import crmdna.email.GAEEmail;
import crmdna.member.Member;
import crmdna.program.Program;
import crmdna.program.ProgramProp;
import crmdna.sequence.Sequence;
import crmdna.sequence.Sequence.SequenceType;
import crmdna.user.User;
import crmdna.user.User.Action;
import crmdna.user.User.ResourceType;

public class Participant {
	public static class ParticipantProp implements Comparable<ParticipantProp> {
		public long participantId;
		public ContactDetailProp contactDetail = new ContactDetailProp();;

		public long programId;
		public long groupId;
		public long memberId;

		@Override
		public int compareTo(ParticipantProp arg0) {
			return contactDetail.firstName
					.compareTo(arg0.contactDetail.firstName);
		}
	}

	public static void emailCSV(String client, long programId, String login)
			throws UnsupportedEncodingException, MessagingException {
		
		User.ensureValidUser(client, login);

		ProgramProp programProp = Program.safeGet(client, programId).toProp();

		List<Key<ParticipantEntity>> keys = ofy(client).load()
				.type(ParticipantEntity.class).filter("programId", programId)
				.keys().list();

		if (keys.size() == 0)
			Utils.throwNotFoundException("No participants found for program ID ["
					+ programId + "]");

		Map<Key<ParticipantEntity>, ParticipantEntity> map = ofy(client).load()
				.keys(keys);

		List<ContactDetailProp> contactDetailProps = new ArrayList<>();

		for (Key<ParticipantEntity> key : map.keySet()) {
			contactDetailProps.add(map.get(key).toProp().contactDetail);
		}

		Collections.sort(contactDetailProps);

		EmailProp emailProp = new EmailProp();

		emailProp.bodyHtml = programProp.getDetailsAsHtml();
		emailProp.bodyHtml += "\nParticipant details are attached.";

		emailProp.csvAttachmentData = ContactDetail.getCSV(contactDetailProps);
		emailProp.sendCSVAttachment = true;
		emailProp.attachmentName = programProp.getNameWOVenue() + ".csv";
		emailProp.subject = "Participants list for program - "
				+ programProp.getNameWOVenue();

		GAEEmail.send(emailProp);
	}

	public static void deleteAllParticipants(String client, long programId,
			String login) {

		Client.ensureValidClient(client);

		ProgramProp programProp = Program.safeGet(client, programId).toProp();
		User.ensureAccess(client, login, ResourceType.PROGRAM,
				programProp.groupProp.groupId, Action.BULKWRITE);

		List<Key<ParticipantEntity>> keys = ofy(client).load()
				.type(ParticipantEntity.class).filter("programId", programId)
				.keys().list();

		if (0 == keys.size()) {
			throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS,
					"No participants data found for programId [" + programId
							+ "]");
		}

		Map<Key<ParticipantEntity>, ParticipantEntity> map = ofy(client).load()
				.keys(keys);

		// delete the program for all member records
		List<Long> memberIds = new ArrayList<>();
		for (Key<ParticipantEntity> key : map.keySet()) {
			ParticipantEntity participantEntity = map.get(key);

			if (participantEntity != null)
				memberIds.add(participantEntity.memberId);
		}

		Member.addOrDeleteProgram(client, programId, memberIds, false, login);

		ofy(client).delete().keys(keys);
	}

	public static void uploadFromMap(String client,
			List<Map<String, String>> listOfMap, long programId, String login) {

		if (null == listOfMap)
			Utils.throwIncorrectSpecException("listOfMap is null");

		Client.ensureValidClient(client);

		List<ContactDetailProp> contactDetailProps = ContactDetail
				.getContactDetailsFromListOfMap(listOfMap);

		uploadFromList(client, contactDetailProps, programId, login);

		return;

	}

	static void uploadFromList(String client,
			List<ContactDetailProp> contactDetailProps, long programId,
			String login) {

		// This function will save the entered details as is into participant
		// entities.
		// If the member is new it will create member entities.

		// if participant details are already saved, just
		// throw an error message and exit

		Client.ensureValidClient(client);
		int count = ofy(client).load().type(ParticipantEntity.class)
				.filter("programId", programId).keys().list().size();

		if (0 != count) {
			throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS, "[" + count
					+ "] participants already found for program id ["
					+ programId
					+ "]. These participants should be deleted first before "
					+ " another upload. ");
		}

		// logic is as follows -

		// for existing members
		// 1. add program to memberentity
		// 2. populate member id to participantentity
		// for new members
		// 1. save as members first and get the member id
		// 2. populate member id to participant entity
		// save all participant entities

		// TODO: also handle cases where both email and mobile number not
		// specified
		// For such cases, do not attempt to merge the record with an existing
		// member.
		// Just create new members

		ProgramProp programProp = Program.safeGet(client, programId).toProp();
		User.ensureAccess(client, login, ResourceType.PROGRAM,
				programProp.groupProp.groupId, Action.BULKWRITE);

		for (ContactDetailProp contactDetailProp : contactDetailProps) {
			if (contactDetailProp.centerId == null)
				contactDetailProp.centerId = programProp.groupProp.groupId;
		}

		contactDetailProps = ContactDetail.sanitizeAndEnsureValid(client,
				contactDetailProps);

		List<Long> existingMemberIds = Member.getMatchingMemberIds(client,
				contactDetailProps);

		List<ParticipantEntity> participantEntities = getParticipantEntities(
				client, contactDetailProps, programId);

		List<ContactDetailProp> toAdd = new ArrayList<>();
		for (int index = 0; index < contactDetailProps.size(); index++) {
			toAdd.add(null);
		}

		for (int index = 0; index < contactDetailProps.size(); index++) {
			ContactDetailProp c = contactDetailProps.get(index);
			ParticipantEntity pe = participantEntities.get(index);
			Long memberId = existingMemberIds.get(index);

			if (memberId == null) {
				toAdd.add(c);
			} else {
				pe.memberId = memberId;
			}
		}

		List<Long> newMemberIds = Member.bulkCreateWithoutValidations(client,
				toAdd, programId);

		for (int index = 0; index < newMemberIds.size(); index++) {
			Long memberId = newMemberIds.get(index);
			if (memberId != null) {
				ParticipantEntity pe = participantEntities.get(index);
				pe.memberId = memberId;
			}
		}

		// add program to existing members
		Member.addOrDeleteProgram(client, programId, existingMemberIds, true,
				login);

		// save all participants entity
		ofy(client).save().entities(participantEntities);
	}

	private static List<ParticipantEntity> getParticipantEntities(
			String client, List<ContactDetailProp> contactDetailProps,
			long programId) {

		// returns list of ids. first id will correspond to first element in
		// contactDetailProps and so on

		List<ParticipantEntity> entities = new ArrayList<>();

		List<Long> ids = Sequence.getNext(client, SequenceType.PARTICIPANT,
				contactDetailProps.size());
		for (int i = 0; i < contactDetailProps.size(); i++) {
			ContactDetailProp c = contactDetailProps.get(i);
			ParticipantEntity pe = new ParticipantEntity();
			pe.participantId = ids.get(i);

			pe.email = c.email;
			pe.firstName = c.firstName;
			pe.lastName = c.lastName;
			pe.gender = c.gender;
			pe.groupId = c.centerId;
			pe.homeAddress = c.homeAddress;
			pe.homePhone = c.homePhone;
			pe.lastName = c.lastName;
			pe.officeAddress = c.officeAddress;
			pe.officePhone = c.officePhone;
			pe.programId = programId;

			entities.add(pe);
		}

		return entities;
	}
}
